﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace Lilium.LExpress
{
    public static class Evaluate
    {
        public static object Eval(this Expression expression)
        {
            if (expression == null) throw new ArgumentNullException("expression");

            switch (expression.NodeType)
            {
                case ExpressionType.Add:
                    return Add((BinaryExpression)expression);
                case ExpressionType.Call:
                    return Eval((MethodCallExpression)expression);
                case ExpressionType.Constant:
                    return Eval((ConstantExpression)expression);
                case ExpressionType.Convert:
                    return Convert((UnaryExpression)expression);
                case ExpressionType.Invoke:
                    return Eval((InvocationExpression)expression);
                case ExpressionType.Lambda:
                    return Eval((LambdaExpression)expression);
                case ExpressionType.MemberAccess:
                    return Eval((MemberExpression)expression);
                default:
                    throw new NotSupportedException("Evaluation of expression of type {0} is not supported".Format(expression.NodeType));
            }
        }

        public static object Eval(this ConstantExpression expression)
        {
            if (expression == null) throw new ArgumentNullException("expression");

            return expression.Value;
        }

        public static object Eval(this InvocationExpression expression)
        {
            return expression.Expression.Eval();
        }

        public static object Eval(this LambdaExpression expression)
        {
            if (expression == null) throw new ArgumentNullException("expression");

            return expression.Body.Eval();
        }

        public static object Eval(this MemberExpression expression)
        {
            if (expression == null) throw new ArgumentNullException("expression");

            var propertyInfo = expression.Member as PropertyInfo;
            if (propertyInfo != null)
            {
                return propertyInfo.GetValue(expression.Expression.Eval(), null);
            }

            var fieldInfo = expression.Member as FieldInfo;
            if (fieldInfo != null)
            {
                return fieldInfo.GetValue(expression.Expression.Eval());
            }

            throw new NotSupportedException("Access to member {0} not supported".Format(expression.Member));
        }

        public static object Eval(this MethodCallExpression expression)
        {
            if (expression == null) throw new ArgumentNullException("expression");

            return expression.Method.Invoke(
                expression.Object != null ? expression.Object.Eval() : null,
                expression.EvalArguments().ToArray());
        }

        public static ConstantExpression EvalToConstant(this Expression expression)
        {
            if (expression == null) throw new ArgumentNullException("expression");

            if (expression.NodeType == ExpressionType.Constant)
                return (ConstantExpression)expression;
            else
                return Expression.Constant(expression.Eval());
        }

        /// <summary>
        /// Evaluates constant arguments.
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static IEnumerable<object> EvalArguments(this MethodCallExpression expression)
        {
            if (expression == null) throw new ArgumentNullException("expression");

            return expression.Arguments.Select(e => e.Eval());
        }

        private static object Add(BinaryExpression binaryExpression)
        {
            throw new NotImplementedException();
        }

        private static object Convert(UnaryExpression expression)
        {
            return expression.Operand.Eval();
        }
    }
}